package com.qsw.qswcommonlib.util;

import android.content.Context;
import android.graphics.Bitmap;
import android.os.Environment;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;

/**
 * 文件系统相关工具
 */
public class CommonFileUtil {
    private static final int DEFAULT_SAVETOFILE_BYTEEACH = 512 * 1024;// 单位：byte

    /**
     * 获取私有路径内的普通文件夹，用户点击清除数据时会被删除
     *
     * @param context 环境
     * @return 普通文件夹绝对路径
     */
    public static String getRootFilePath(Context context) {
        return context.getExternalFilesDir(null).getAbsolutePath();
    }

    /**
     * 获取私有路径内的缓存文件夹，用户点击清除缓存时会被删除
     *
     * @param context 环境
     * @return 缓存文件夹绝对路径
     */
    public static String getRootCacheFilePath(Context context) {
        return context.getExternalCacheDir().getAbsolutePath();
    }

    /**
     * 获取内部存储路径
     *
     * @return 内部存储路径
     */
    public static String getInnerSDCardPath() {
        return Environment.getExternalStorageDirectory().getPath();
    }

    /**
     * 获取所有SD卡路径（测试并不一定能获取所有挂载）
     *
     * @return 所有结果
     */
    public static List<String> getExtSDCards(Context context) {
        File[] files = context.getExternalFilesDirs(Environment.MEDIA_MOUNTED);
        String path;
        String[] splitStrs;
        String innerPath = getInnerSDCardPath();
        List<String> result = new ArrayList<>();
        for (File file : files) {
            if (file == null) {
                continue;
            }
            path = file.getAbsolutePath();
            splitStrs = path.split(File.separator + "Android");
            if (splitStrs.length > 1 && !innerPath.equals(splitStrs[0])) {
                result.add(splitStrs[0]);
            }
        }
        return result;
    }

    /**
     * 获取已挂载的所有存储卷信息（测试可以获取所有挂载），需要AndroidSDK 30
     *
     * @return 外部存储卷信息
     */
    /*public static List<VolumeInfo> getExtSDCardsInfo(Context context) {
        StorageManager storageManager = (StorageManager) context.getSystemService(Activity.STORAGE_SERVICE);
        return storageManager.getVolumes();
    }*/

    /**
     * 获取DiskId中包含指定sign的已挂载外部存储卷信息（测试发行可以遍历到所有挂载），需要AndroidSDK 30
     *
     * @param sign 区分存储的标记
     * @return 外部存储卷信息
     */
    /*public static VolumeInfo getExtSDCardInfo(String sign, Context context) {
        List<VolumeInfo> volumes = getExtSDCardsInfo(context);
        String deskId;
        VolumeInfo resultSDCardInfo = null;
        for (VolumeInfo volume : volumes) {
            deskId = volume.getDiskId();
            if (!TextUtils.isEmpty(deskId)
                    && deskId.contains(sign)) {
                if (volume.getState() == VolumeInfo.STATE_MOUNTED) {
                    resultSDCardInfo = volume;
                }
                break;
            }
        }
        return resultSDCardInfo;
    }*/

    /**
     * 卸载外部存储卡，需要权限（具体权限懒得查），需要AndroidSDK 30
     *
     * @return 卸载结果
     */
    /*public static boolean unmountExtUSBDisk(VolumeInfo extSDCardInfo, Context context) {
        if (extSDCardInfo == null) {
            return false;
        }

        StorageManager storageManager = (StorageManager) context.getSystemService(Activity.STORAGE_SERVICE);
        storageManager.unmount(extSDCardInfo.getId());

        return true;
    }*/

    /**
     * 将Asset中的文件复制到指定文件
     *
     * @param context        环境
     * @param fromAssetsFile 资源文件相对地址
     * @param savePath       要保存的路径
     * @param saveFileName   要保存的文件名
     * @param deleteIfExists true：若目标文件存在则删除目标文件重新保存；false：若目标文件存在则不操作直接返回true
     * @return true：成功；false：失败
     * @throws IOException 文件操作异常
     */
    public static boolean saveToFile(Context context, String fromAssetsFile, String savePath, String saveFileName, boolean deleteIfExists) throws IOException {
        InputStream is = context.getResources().getAssets().open(fromAssetsFile);
        return saveToFile(is, savePath, saveFileName, deleteIfExists);
    }

    /**
     * 将流中数据保存到指定文件，默认每次从流中读取1024字节，操作完会自动关闭输入流
     *
     * @param fromIS         源数据流
     * @param savePath       要保存的路径
     * @param saveFileName   要保存的文件名
     * @param deleteIfExists true：若目标文件存在则删除目标文件重新保存；false：若目标文件存在则不操作直接返回true
     * @return true：成功；false：失败
     * @throws IOException 文件操作异常
     */
    public static boolean saveToFile(InputStream fromIS, String savePath, String saveFileName, boolean deleteIfExists) throws IOException {
        return saveToFile(fromIS, savePath, saveFileName, DEFAULT_SAVETOFILE_BYTEEACH, deleteIfExists);
    }

    /**
     * 将流中数据保存到指定文件，操作完会自动关闭输入流
     *
     * @param fromIS         源数据流
     * @param savePath       要保存的路径
     * @param saveFileName   要保存的文件名
     * @param byteEach       保存时每次从流中读取的字节数
     * @param deleteIfExists true：若目标文件存在则删除目标文件重新保存；false：若目标文件存在则不操作直接返回true
     * @return true：成功；false：失败
     * @throws IOException 文件操作异常
     */
    public static boolean saveToFile(InputStream fromIS, String savePath, String saveFileName, int byteEach, boolean deleteIfExists) throws IOException {
        return saveToFile(fromIS, savePath, saveFileName, byteEach, deleteIfExists, null);
    }

    /**
     * 将流中数据保存到指定文件，操作完会自动关闭输入流
     *
     * @param fromIS                     源数据流
     * @param savePath                   要保存的路径
     * @param saveFileName               要保存的文件名
     * @param deleteIfExists             true：若目标文件存在则删除目标文件重新保存；false：若目标文件存在则不操作直接返回true
     * @param onFileSaveProgressListener 当文件保存进度改变时的回调
     * @return true：成功；false：失败
     * @throws IOException 文件操作异常
     */
    public static boolean saveToFile(InputStream fromIS, String savePath, String saveFileName, boolean deleteIfExists,
                                     OnFileSaveProgressListener onFileSaveProgressListener) throws IOException {
        return saveToFile(fromIS, savePath, saveFileName, DEFAULT_SAVETOFILE_BYTEEACH, deleteIfExists, onFileSaveProgressListener);
    }

    /**
     * 将流中数据保存到指定文件，操作完会自动关闭输入流
     *
     * @param fromIS                     源数据流
     * @param savePath                   要保存的路径
     * @param saveFileName               要保存的文件名
     * @param byteEach                   保存时每次从流中读取的字节数
     * @param deleteIfExists             true：若目标文件存在则删除目标文件重新保存；false：若目标文件存在则不操作直接返回true
     * @param onFileSaveProgressListener 当文件保存进度改变时的回调
     * @return true：成功；false：失败
     * @throws IOException 文件操作异常
     */
    public static boolean saveToFile(InputStream fromIS, String savePath, String saveFileName, int byteEach, boolean deleteIfExists,
                                     OnFileSaveProgressListener onFileSaveProgressListener) throws IOException {
        String saveFilePath = tryCreateFile(savePath, saveFileName, deleteIfExists);
        if (saveFilePath == null) {
            fromIS.close();
            return false;
        }
        File saveFile = new File(saveFilePath);

        byte[] buffer = new byte[byteEach];
        FileOutputStream fos = new FileOutputStream(saveFile);
        int savedSize = 0;
        int readSize;
        while ((readSize = fromIS.read(buffer)) > 0) {
            fos.write(buffer, 0, readSize);
            fos.flush();
            if (onFileSaveProgressListener != null) {
                savedSize += readSize;
                onFileSaveProgressListener.onFileSaveProgress(savedSize);
            }
        }

        fromIS.close();
        fos.close();

        return true;
    }

    /**
     * 数据保存为文件
     *
     * @param fromData       源数据
     * @param savePath       要保存的路径
     * @param saveFileName   要保存的文件名
     * @param deleteIfExists true：若目标文件存在则删除目标文件重新保存；false：若目标文件存在则不操作直接返回true
     * @return true：成功；false：失败
     * @throws IOException 文件操作异常
     */
    public static boolean saveToFile(byte[] fromData, String savePath, String saveFileName, boolean deleteIfExists) throws IOException {
        String saveFilePath = tryCreateFile(savePath, saveFileName, deleteIfExists);
        if (saveFilePath == null) {
            return false;
        }
        File saveFile = new File(saveFilePath);

        FileOutputStream fos = new FileOutputStream(saveFile);
        fos.write(fromData, 0, fromData.length);
        fos.flush();

        fos.close();

        return true;
    }

    /**
     * 保存为图片
     *
     * @param fromBitmap     源数据
     * @param savePath       要保存的路径
     * @param saveFileName   要保存的文件名
     * @param deleteIfExists true：若目标文件存在则删除目标文件重新保存；false：若目标文件存在则不操作直接返回true
     * @return true：成功；false：失败
     * @throws IOException 文件操作异常
     */
    public static boolean saveToFile(Bitmap fromBitmap, String savePath, String saveFileName, boolean deleteIfExists) throws IOException {
        String saveFilePath = tryCreateFile(savePath, saveFileName, deleteIfExists);
        if (saveFilePath == null) {
            return false;
        }
        File saveFile = new File(saveFilePath);

        FileOutputStream fos = new FileOutputStream(saveFile);
        fromBitmap.compress(Bitmap.CompressFormat.PNG, 100, fos);
        fos.flush();

        fos.close();

        return true;
    }

    /**
     * 尝试创建文件，若路径不存在则创建路径，若文件不存在则创建文件
     *
     * @param filePath       要检查的文件路径
     * @param fileName       要检查的文件名
     * @param deleteIfExists true：若目标文件存在则删除目标文件重新创建后返回文件路径；false：若目标文件存在则直接返回文件路径
     * @return 若目标文件已存在或创建成功则返回文件绝对路径，否则返回null
     * @throws IOException 文件操作异常
     */
    public static String tryCreateFile(String filePath, String fileName, boolean deleteIfExists) throws IOException {
        File file = new File(filePath);

        boolean result = true;
        if (!file.exists()) {
            result = file.mkdirs();
        }
        if (!result) {
            return null;
        }

        file = new File(filePath + File.separator + fileName);
        if (file.exists()) {
            if (deleteIfExists) {
                result = file.delete();
            } else {
                return file.getAbsolutePath();
            }
        }
        if (!result) {
            return null;
        }

        result = file.createNewFile();

        return result ? file.getAbsolutePath() : null;
    }

    /**
     * 复制该目录下的所有文件夹及文件
     *
     * @param fromFolder     源文件夹
     * @param toFolderPath   目标文件夹路径
     * @param deleteIfExists true：若目标文件存在则删除目标文件重新保存；false：若目标文件存在则不操作该文件默认为成功
     * @return true：成功；false：失败
     * @throws IOException 文件操作异常
     */
    public static boolean copyListFiles(File fromFolder, String toFolderPath, boolean deleteIfExists) throws IOException {
        if (!fromFolder.exists()) {
            return false;
        }
        boolean result = true;

        File toFolder = new File(toFolderPath);
        if (!toFolder.exists()) {
            result = toFolder.mkdirs();
        }
        if (!result) {
            return result;
        }

        if (fromFolder.isFile()) {
            return copyFileToFolder(fromFolder.getAbsolutePath(), toFolderPath, deleteIfExists);
        }

        File[] listFiles = fromFolder.listFiles();
        if (listFiles == null || listFiles.length == 0) {
            return result;
        }

        for (File file : listFiles) {
            if (file.isDirectory()) {
                String folderName = file.getAbsolutePath().substring(file.getAbsolutePath().lastIndexOf(File.separator));
                result &= copyListFiles(file, toFolderPath + File.separator + folderName, deleteIfExists);
            } else {
                result &= copyFileToFolder(file.getAbsolutePath(), toFolderPath, deleteIfExists);
            }
        }

        return result;
    }

    /**
     * 将指定文件复制到指定文件夹，若源文件为文件夹，则直接在目标文件夹内创建文件夹后返回
     *
     * @param fromFilePath   源文件路径
     * @param toFolderPath   目标文件夹
     * @param deleteIfExists true：若目标文件存在则删除目标文件重新保存；false：若目标文件存在则不操作该文件默认为成功
     * @return true：成功；false：失败
     * @throws IOException 文件操作异常
     */
    public static boolean copyFileToFolder(String fromFilePath, String toFolderPath, boolean deleteIfExists) throws IOException {
        return copyFileToFolder(fromFilePath, toFolderPath, deleteIfExists, null);
    }

    /**
     * 将指定文件复制到指定文件夹，若源文件为文件夹，则直接在目标文件夹内创建文件夹后返回
     *
     * @param fromFilePath               源文件路径
     * @param toFolderPath               目标文件夹
     * @param deleteIfExists             true：若目标文件存在则删除目标文件重新保存；false：若目标文件存在则不操作该文件默认为成功
     * @param onFileSaveProgressListener 当文件保存进度改变时的回调
     * @return true：成功；false：失败
     * @throws IOException 文件操作异常
     */
    public static boolean copyFileToFolder(String fromFilePath, String toFolderPath, boolean deleteIfExists, OnFileSaveProgressListener onFileSaveProgressListener) throws IOException {
        File fromFile = new File(fromFilePath);
        if (!fromFile.exists()) {
            return false;
        }

        File toFolder = new File(toFolderPath);
        if (!toFolder.exists() && !toFolder.mkdirs()) {
            return false;
        }

        String fromFileName = fromFilePath.substring(fromFilePath.lastIndexOf(File.separator));

        if (fromFile.isDirectory()) {
            if (onFileSaveProgressListener != null) {
                onFileSaveProgressListener.onFileSaveProgress(0);
            }
            return new File(toFolderPath + File.separator + fromFileName).mkdirs();
        }

        return copyFile(fromFilePath, toFolderPath + File.separator + fromFileName, deleteIfExists, onFileSaveProgressListener);
    }

    /**
     * 将指定文件复制为指定文件，若源文件类型为文件夹，则返回失败
     *
     * @param fromFilePath   源文件路径
     * @param toFilePath     目标文件路径
     * @param deleteIfExists true：若目标文件存在则删除目标文件重新保存；false：若目标文件存在则不操作该文件默认为成功
     * @return true：成功；false：失败
     * @throws IOException 文件操作异常
     */
    public static boolean copyFile(String fromFilePath, String toFilePath, boolean deleteIfExists) throws IOException {
        return copyFile(fromFilePath, toFilePath, deleteIfExists, null);
    }

    /**
     * 将指定文件复制为指定文件，若源文件类型为文件夹，则返回失败
     *
     * @param fromFilePath               源文件路径
     * @param toFilePath                 目标文件路径
     * @param deleteIfExists             true：若目标文件存在则删除目标文件重新保存；false：若目标文件存在则不操作该文件默认为成功
     * @param onFileSaveProgressListener 当文件保存进度改变时的回调
     * @return true：成功；false：失败
     * @throws IOException 文件操作异常
     */
    public static boolean copyFile(String fromFilePath, String toFilePath, boolean deleteIfExists, OnFileSaveProgressListener onFileSaveProgressListener) throws IOException {
        return copyFile(fromFilePath, toFilePath, DEFAULT_SAVETOFILE_BYTEEACH, deleteIfExists, onFileSaveProgressListener);
    }

    /**
     * 将指定文件复制为指定文件，若源文件类型为文件夹，则返回失败
     *
     * @param fromFilePath               源文件路径
     * @param toFilePath                 目标文件路径
     * @param byteEach                   保存时每次从流中读取的字节数
     * @param deleteIfExists             true：若目标文件存在则删除目标文件重新保存；false：若目标文件存在则不操作该文件默认为成功
     * @param onFileSaveProgressListener 当文件保存进度改变时的回调
     * @return true：成功；false：失败
     * @throws IOException 文件操作异常
     */
    public static boolean copyFile(String fromFilePath, String toFilePath, int byteEach, boolean deleteIfExists, OnFileSaveProgressListener onFileSaveProgressListener) throws IOException {
        File fromFile = new File(fromFilePath);
        if (!fromFile.exists() || fromFile.isDirectory()) {
            return false;
        }

        String toFolderPath = toFilePath.substring(0, toFilePath.lastIndexOf(File.separator));
        String toFileName = toFilePath.substring(toFilePath.lastIndexOf(File.separator) + 1);
        String createFilePath = tryCreateFile(toFolderPath, toFileName, deleteIfExists);
        if (createFilePath == null) {
            return false;
        }

        FileInputStream fis = new FileInputStream(fromFile);
        return saveToFile(fis, toFolderPath, toFileName, byteEach, deleteIfExists, onFileSaveProgressListener);
    }

    /**
     * 删除该目录下的所有文件夹及文件
     *
     * @param rootFolder 待删除文件夹
     * @return true：成功；false：失败
     */
    public static boolean deleteListFiles(File rootFolder) {
        if (!rootFolder.exists()) {
            return true;
        }
        if (rootFolder.isFile()) {
            return rootFolder.delete();
        }
        File[] listFiles = rootFolder.listFiles();

        boolean result = true;
        for (File file : listFiles) {
            if (file.isDirectory()) {
                result &= deleteListFiles(file);
            } else {
                result &= file.delete();
            }
        }
        result &= rootFolder.delete();

        return result;
    }

    /**
     * 删除该文件，若为文件夹则返回false
     *
     * @param file 待删除文件
     * @return true：成功；false：失败
     */
    public static boolean deleteFile(File file) {
        if (!file.exists()) {
            return true;
        }
        if (file.isDirectory()) {
            return false;
        }

        return file.delete();
    }

    /**
     * 删除该文件，若为文件夹则返回false
     *
     * @param filePath 待删除文件路径
     * @return true：成功；false：失败
     */
    public static boolean deleteFile(String filePath) {
        return deleteFile(new File(filePath));
    }

    /**
     * 获取该目录及其子目录下的所有文件
     *
     * @param folderPath    根目录文件夹路径
     * @param containFolder true：在某文件夹内文件全部统计后加入该文件夹；false：不包含文件夹只统计文件
     * @return 该目录及其子目录下的所有文件数组
     */
    public static List<File> listAllFiles(String folderPath, boolean containFolder) {
        File folder = new File(folderPath);
        if (!folder.exists() || !folder.isDirectory()) {
            return new ArrayList<>();
        }
        List<File> result = new LinkedList<>();
        File[] files = folder.listFiles();
        for (File file : files) {
            if (!file.exists()) {
                continue;
            }
            if (file.isFile()) {
                result.add(file);
            } else if (file.isDirectory()) {
                result.addAll(listAllFiles(file.getAbsolutePath(), containFolder));
                if (containFolder) {
                    result.add(file);
                }
            }
        }
        return result;
    }

    /**
     * 获取文件后缀
     *
     * @param fileName 文件名
     * @return 文件后缀，若没有后缀则返回空字符串
     */
    private String getFileSuffix(String fileName) {
        if (fileName == null) {
            return null;
        }
        int lastIndex = fileName.lastIndexOf(".");
        String suffix = lastIndex < 0 ? "" : fileName.substring(lastIndex);
        return suffix.length() > 1 ? suffix.substring(1) : suffix;
    }

    /**
     * 修改文件后缀
     *
     * @param file      待修改文件
     * @param newSuffix 新的后缀
     * @return true：成功；false：失败
     */
    public static File changeFileSuffix(File file, String newSuffix) {
        if (file == null || !file.exists()) {
            return null;
        }

        // 获取文件原来的目录
        String directoryPath = file.getParent();
        // 新的文件名
        String newFileName = getFileNameOfNewSuffix(file.getName(), newSuffix);

        // 创建新的文件对象
        File newFile = new File(directoryPath, newFileName);
        // 重命名文件
        return file.renameTo(newFile) ? newFile : null;
    }

    /**
     * 获取修改后缀后的文件名
     *
     * @param fileName  待修改文件名
     * @param newSuffix 新的后缀
     * @return 修改后的文件名
     */
    public static String getFileNameOfNewSuffix(String fileName, String newSuffix) {
        if (fileName == null) {
            return newSuffix;
        }

        // 获取文件名字不包含后缀的部分
        int lastIndexOfPoint = fileName.lastIndexOf(".");
        String fileNameWithoutSuffix = lastIndexOfPoint < 0 ? fileName : fileName.substring(0, lastIndexOfPoint);
        // 新的文件名
        return fileNameWithoutSuffix + ((newSuffix == null) ? "" : ("." + newSuffix));
    }

    /**
     * 当文件保存进度改变时的回调
     */
    public interface OnFileSaveProgressListener {
        /**
         * 当文件保存进度改变时的回调
         *
         * @param savedLength 当前已保存长度
         */
        void onFileSaveProgress(int savedLength);
    }
}
